const mosca = require('mosca');
const redis = require('redis');
const CFG = require('../config');
const FUN = require('../comm/func');
const timLib = require('../comm/time-lib');
const tbDevice = require('../model/tb_device');
const tbUserId = require('../model/tb_user_id');

/* 构建了一个mqtt服务器
 端口8883: php发布同步消息，同步策略改动，发送命令给dev
 端口8884: 前端面板通过wss连接到mq，用于策略报警alarm和策略更新update
 端口8885: 对ssl的支持，如果要支持mqtt ssl或者wss，需开启
 */

const MQ_USER = ['wpass', 'gw', 'admin', 'php', 'dtu']; // mq内定用户
const MQ_AUTO_USER = ['login', 'vs']; // mq自分配用户
const VENDOR_DEVS = [5393073]; // 白名单，解决旧平台设备密码和新平台不匹配问题

const moscaSettings = {
  port: CFG.MqttSrv.port, // 设置监听端口8883
  maxInflightMessages: 10240, // 设置单条消息的最大长度,超出后服务端会返回
  allowNonSecure: true, // 允许非ssl连接（即普通连接）
　https: { // 设置WebSocket参数
    port: CFG.MqttSrv.wss_port, // 8884
    bundle:true,
    static:'./'
  },
  secure: { // 设置ssl参数
    port: CFG.MqttSrv.ssl_port, // 8885
    keyPath: CFG.MqttSrv.ssl_key,
    certPath: CFG.MqttSrv.ssl_crt,
  },
  persistence: { // 持久化，对retained的支持
    factory: mosca.persistence.Redis,
    host: CFG.Redis.host,
    port: CFG.Redis.port,
    password: CFG.Redis.auth_pass,
    db: 2,
    ttl: {
      subscriptions: 1000 * 60 * 10, // the time (ms) after which subscriptions will expire. It defaults to 1 hour.
      packets: 1000 * 60 * 10 // the time (ms) after which packets will expire.
    }
  },
}

const server = new mosca.Server(moscaSettings);
server.on('ready',()=>{
  server.authenticate = authenticate;//授权验证函数
  server.authorizePublish = authorizePublish;
  server.authorizeSubscribe = authorizeSubscribe;
  console.log(timLib.NowStr(), 'Mosca server start at port', CFG.MqttSrv.port);
  global.shareEvt.emit('mqtt_srv_ready');
})

server.on('err',()=>{
  outErr('Mosca server error!');
})

const actCacheNum = 50; // 记录上限
const actDevList = new Map(); // 活跃设备列表 hwid->{auth,hwType,stamp}
const actUserList = new Map(); // 活跃用户列表 uid->{auth,stamp}

// 检查一个设备有没有登录权限
async function CheckActDev(hwName, id)
{
  id = ~~id;
  if(id == 0) return false;

  let hwtype = CFG.HwType.indexOf(hwName);
  if (hwtype <= 0) return false;

  let devObj = actDevList.get(id);
  if (devObj) {
    if (devObj.hwtype == hwtype && devObj.auth == true) {
      devObj.stamp = timLib.NowStamp();
      actDevList.delete(id);
      actDevList.set(id, devObj);
      return true;
    } else { // 防止无效设备频繁攻击
      return false;
    }
  }

  // 查询数据库
  let newDevObj = {auth:false, hwtype:hwtype, stamp:timLib.NowStamp()};
  let record = await tbDevice.find(id);
  if (record && record.hwtype == hwtype) {
    newDevObj.auth = true;
  }
  actDevList.set(id, newDevObj);
  tidyActList(actDevList, actCacheNum);

  return newDevObj.auth;
}

// 查询用户是否存在，以及是否存在相关权限
async function CheckUserClient(uid, key)
{
  let id = ~~uid;
  if(id == 0) return false;

  let userObj = actUserList.get(id);
  if (userObj) {
    if (userObj.key == key && userObj.auth == true) {
      userObj.stamp = timLib.NowStamp();
      actUserList.delete(id);
      actUserList.set(id, userObj);
      return true;
    } else { // 防止无效用户频繁攻击
      return false;
    }
  }

  // 查询用户数据库
  let newUserObj = {auth:false, key:key, stamp:timLib.NowStamp()};
  let field = await tbUserId.getFields(id, 'auth');
  if (field && field.auth && field.auth.split(',').includes(key)) {
    newUserObj.auth = true;
  }
  actUserList.set(id, newUserObj);
  tidyActList(actUserList, actCacheNum);

  return newUserObj.auth;
}

// 清除多余的列表项目
function tidyActList(mapList, limit)
{
  let count = 10; // 每次删减的数量
  if (mapList.size > limit) { // 删除多余的
    for (let id of mapList.keys()) {
      if (--count < 0) break;
      mapList.delete(id);
    }
  }
}

// 当db变化时，同步清除缓存，涉及设备表，用户表改动
function ClearActList(type)
{
  if (type.includes('dev')) {
    outLog(' - clean mq srv dev list');
    actDevList.clear();
  }
  if (type.includes('user')) {
    outLog(' - clean mq srv user list');
    actUserList.clear();
  }
}

// 调试打印
function DebugActList()
{
  console.log('actDevList', actDevList);
  console.log('actUserList', actUserList);
}

//授权验证处理
async function authenticate(client, username, password, callback)
{
  if(!username || !password) return callback(null, false);

  let id = client.id.split('.');
  if (id == null || id.length < 2) { // id非法
    console.log(timLib.NowStr(), 'mq client id error', client.id);
    return callback(null, false);
  }

  let check = false;
  let userId = id[1];
  let userPwd = password.toString();

  if (MQ_USER.includes(username) == false && MQ_AUTO_USER.includes(username) == false)
    return callback(null, false);
  client.user = username;

  if (MQ_AUTO_USER.includes(username)) { // 自动分配的，按授权密码和权限字段来
    if (CFG.CheckPassword(userId, userPwd)) {
      check = await CheckUserClient(userId, username); // 检查在不在user表并拥有对应权限
    }
  } else if (username == 'dtu') { // dtu检查授权密码，在不在dev表
    if (CFG.CheckPassword(userId, userPwd)) { // 检查授权密码
      check = await CheckActDev(username, userId);
    }
  } else if (username == 'gw' || username == 'wpass') { // 设备组用固定密码, 'wpass', 'gw'
    if (userPwd == '888123' || VENDOR_DEVS.includes(~~userId)) {
      check = await CheckActDev(username, userId); // 检查在不在dev表
    }
  } else if (username == 'admin') { // 后台用户，固定密码
    if (userPwd == '123888') check = true;
  } else if (username == 'php') { // 后台用户，固定密码
    if (userPwd == '123888') check = true;
  }

  if (check) {
    client.login_stamp = timLib.NowStamp();
    console.log(timLib.NowStr(), 'online', username, id);
    return callback(null, true);
  }

  console.log(timLib.NowStr(), 'Unauthorized devices:', username, userId, userPwd);
  return callback(null, false);
}

//发布验证
function authorizePublish(client,topic,payload,callback)
{
  let authorized=false;

  if (client.user=='dtu') {
    let id = client.id.split('.');
    let hwid = id[1];
    let regExp1 = new RegExp(`^dtu\/(?:cmdres|json|upgrade|log)\/${hwid}$`);
    let regExp2 = new RegExp(`^.+\/dtu\/(?:rx|tx|data)\/${hwid}$`); // dtu只能发布+/dtu/tx|rx|json|****等特定主题
    let regExp3 = new RegExp(`^.+\/dtu\/(?:file|file-opt)\/${hwid}\/.+$`); // 文件类
    let regExp4 = new RegExp(`^.+\/dtu\/var\/.+-${hwid}$`); // +/dtu/var/xxx-id
    let regExp5 = new RegExp(`^${hwid}\/(?:online|offline)$`);
    if (topic.match(regExp1) || topic.match(regExp2) || topic.match(regExp3) ||
      topic.match(regExp4) || topic.match(regExp5)) {
      authorized=true;
    } else {
      console.log(timLib.NowStr(), id, 'NOT ALLOW PUB:', topic);
    }
  } else {
    authorized=true;
  }

  callback(null,authorized);
}

//订阅验证
function authorizeSubscribe(client,topic,callback)
{
  callback(null, true);
}

//客户端链接
server.on('clientConnected', function(client){
  let id = client.id.split('.');
  let clientIp = client.connection.stream.remoteAddress;
  let message = {
    topic:id[0]+'/online',
    payload:id[1]+' '+clientIp,
    qos:0, retain:false
  }

  //console.log('client connected', client.id);
  server.publish(message);
})

//收到消息
server.on('published', function(packet, client) {
  if(packet.topic.match(/^[$]SYS\//g)) //系统主题
  {

  }
  else //用户主题
  {
    //console.log('Top',packet.topic,packet.payload.toString().substring(0,100));
  }
})

//客户端订阅了主题
// server.on('subscribed', function(topic,client){
//   console.log('subscribed : ', topic);
// })

//客户端取消订阅
// server.on('unsubscribed', function(topic,client){
//   console.log('unsubscribed : ', topic);
// })

//客户端完全断开
server.on('clientDisconnected', function(client) {
  //gw和wpass设备自己会发遗愿消息
  let id = client.id.split('.');
  if (id && id.length==2 && id[0]=='dtu') {
    let message = { // dtu附加遗愿消息
      topic:id[0]+'/offline', payload:id[1],
      qos:0, retain:false
    }
    server.publish(message);
    console.log(timLib.NowStr(), 'client Disconnected', client.id, client.user);
  }

  let time = (timLib.NowStamp() - client.login_stamp) / 1000;
  console.log(timLib.NowStr(), 'offline', id, parseInt(time), 'S');
})

exports.DebugActList = DebugActList;
exports.ClearActList = ClearActList;